#ifndef SIMPLE_SUPPORT_RANGE_HPP
#define SIMPLE_SUPPORT_RANGE_HPP

#include <algorithm>
#include <type_traits>
#include <utility>
#include <cassert>

#include "array.hpp"
#include "array_operators.hpp"
#include "algorithm/traits.hpp"
#include "type_traits/misc.hpp" // non_deduced

namespace simple::support
{

	template <typename Type, typename Bounds = array<Type,2>>
	struct range
	{
		Bounds bounds;

		using bounds_type = Bounds;
		using iterator = std::conditional_t<is_iterable<Type>{},
			Type, void>;

		constexpr static auto limit()
		{
			static_assert(std::numeric_limits<Type>::is_specialized);
			return support::range{ std::numeric_limits<Type>::lowest(), std::numeric_limits<Type>::max() };
		}

		constexpr auto& lower() noexcept { return bounds[0]; }
		constexpr auto& upper() noexcept { return bounds[1]; }
		constexpr const auto& lower() const noexcept { return bounds[0]; }
		constexpr const auto& upper() const noexcept { return bounds[1]; }

		constexpr bool operator ==(const range& other) const
		{ return bounds == other.bounds; }
		constexpr bool operator !=(const range& other) const
		{ return !(*this == other); }

		template <typename It = Type,
			std::enable_if_t<is_iterable<It>{}>...>
		[[nodiscard]] constexpr Type begin() const { return lower(); }
		template <typename It = Type,
			std::enable_if_t<is_iterable<It>{}>...>
		[[nodiscard]] constexpr Type end() const { return upper(); }

		template <typename It = Type,
			std::enable_if_t<is_iterable<It>{}>...>
		[[nodiscard]] constexpr auto rbegin() const
		{ return std::make_reverse_iterator(upper()); }
		template <typename It = Type,
			std::enable_if_t<is_iterable<It>{}>...>
		[[nodiscard]] constexpr auto rend() const
		{ return std::make_reverse_iterator(lower()); }

		template<typename OtherType, typename OtherBounds = Bounds>
		[[nodiscard]] constexpr
		auto sub_range(range<OtherType, OtherBounds> other) const
		{
			// TODO: use unsigned for other type when it makes sense, to avoid overflow
			clamp_in_place(other, {
				OtherType{},
				static_cast<OtherType>(upper() - lower())
			});
			return support::range
			{
				lower() + other.lower(),
				lower() + other.upper()
			};
		}

		constexpr auto validity() const { return lower() <= upper(); }
		constexpr bool valid() const { return bool(validity()); }

		constexpr range& fix() &
		{
			using std::min;
			using std::max;
			auto temp = bounds;
			lower() = min(temp[0], temp[1]);
			upper() = max(temp[0], temp[1]);
			return *this;
		}

		[[nodiscard]]
		constexpr range fix() &&
		{
			this->fix();
			return *this;
		}

		[[nodiscard]]
		constexpr range fix() const &
		{
			return range{*this}.fix();
		}

		[[nodiscard]]
		constexpr auto fixed() const
		{
			using std::min;
			using std::max;
			return support::range
			{
				min(lower(), upper()),
				max(lower(), upper())
			};
		}

		[[nodiscard]]
		constexpr auto reverse() const
		{
			return support::range{rbegin(), rend()};
		}

		// TODO: constraint too strict
		template <typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
		constexpr bool contains(const ValueType& value) const
		{ return lower() < value && value < upper(); }

		template <typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
		constexpr bool intersects(const ValueType& value) const
		{ return lower() <= value && value <= upper(); };

		template <typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
		constexpr bool intersects_lower(const ValueType& value) const
		{ return lower() <= value && value < upper(); }

		template <typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
		constexpr bool intersects_upper(const ValueType& value) const
		{ return lower() < value && value <= upper(); }

		// TODO: make these more generic, compatible Type, any Bounds
		constexpr bool contains(const range& other) const
		{ return lower() < other.lower() && other.upper() < upper(); }

		constexpr bool covers(const range& other) const
		{ return lower() <= other.lower() && other.upper() <= upper(); }

		constexpr bool overlaps(const range& other) const
		{ return lower() < other.upper() && upper() > other.lower(); }

		constexpr bool intersects(const range& other) const
		{ return lower() <= other.upper() && upper() >= other.lower(); }
		// { return intersection(other).valid(); } // why is the cool way always inefficient :/

		constexpr range& intersect(const range& other)
		{
			using std::min;
			using std::max;
			lower() = max(lower(), other.lower());
			upper() = min(upper(), other.upper());
			return *this;
		}

		constexpr auto intersection(const range& other) const
		{
			using std::min;
			using std::max;
			return support::range
			{
				max(lower(), other.lower()),
				min(upper(), other.upper())
			};
		}

		template <typename Other, std::enable_if_t<
			std::is_convertible_v<Type, Other>
		>* = nullptr>
		operator range<Other> ()
		{
			return
			{
				static_cast<Other>(lower()),
				static_cast<Other>(upper())
			};
		}

		template <typename Other, std::enable_if_t<
			!std::is_convertible_v<Type, Other> &&
			std::is_constructible_v<Type, Other>
		>* = nullptr>
		explicit operator range<Other> ()
		{
			return
			{
				static_cast<Other>(lower()),
				static_cast<Other>(upper())
			};
		}

	};

	template <typename T> range(T lower, T upper) -> range<T>;

	// TODO: generalize some of these to anything that has lower()/upper() bound iterface
	// and implement the members based on these instead of these based on members
	template <typename Type, typename Bounds = array<Type,2>, typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
	constexpr bool contains(const range<Type,Bounds>& range, const ValueType& value)
	{ return range.contains(value); }

	template <typename Type, typename ValueType, typename Bounds = array<Type,2>, std::common_type_t<ValueType, Type>* = nullptr>
	constexpr bool intersects(const range<Type,Bounds>& range, const ValueType& value)
	{ return range.intersects(value); }

	template <typename Type, typename ValueType, typename Bounds = array<Type,2>, std::common_type_t<ValueType, Type>* = nullptr>
	constexpr bool intersects_lower(const range<Type,Bounds>& range, const ValueType& value)
	{ return range.intersects_lower(value); }

	// TODO: here to enable implicit conversion when Type is specified, but this whole business is really dodgy
	template <typename Type, typename ValueType = Type, std::common_type_t<ValueType, Type>* = nullptr>
	constexpr bool intersects_lower(const non_deduced<range<Type>>& range, const non_deduced<ValueType>& value)
	{ return intersects_lower(range, value); }

	template <typename Type, typename ValueType, typename Bounds = array<Type,2>, std::common_type_t<ValueType, Type>* = nullptr>
	constexpr bool intersects_upper(const range<Type,Bounds>& range, const ValueType& value)
	{ return range.intersects_upper(value); }

	template <typename Type>
	constexpr bool contains(const range<Type>& one, const range<Type>& other)
	{ return one.contains(other); }

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr bool contains(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
	{ return one.contains(other); }

	template <typename Type>
	constexpr bool covers(const range<Type>& one, const range<Type>& other)
	{ return one.covers(other); }

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr bool covers(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
	{ return one.covers(other); }

	template <typename Type>
	constexpr bool overlaps(const range<Type>& one, const range<Type>& other)
	{ return one.overlaps(other); }

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr bool overlaps(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
	{ return one.overlaps(other); }

	template <typename Type>
	constexpr bool intersects(const range<Type>& one, const range<Type>& other)
	{ return one.intersects(other); }

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr bool intersects(const range<Type,Bounds>& one, const range<Type,Bounds>& other)
	{ return one.intersects(other); }

	template <typename Type>
	constexpr auto intersection(const range<Type>& one, range<Type> other)
	{ return one.intersection(other); }

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr auto intersection(const range<Type,Bounds>& one, range<Type,Bounds> other)
	{ return one.intersection(other); }

	template <typename Type, typename Bounds = array<Type,2>>
	[[nodiscard]]
	constexpr auto reverse(const range<Type, Bounds>& one)
	{ return one.reverse(); }

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr
	range<Type>& clamp_in_place(range<Type,Bounds>& v, const range<Type,Bounds>& hilo)
	{
		using std::clamp;
		for(size_t i = 0; i < v.bounds.size(); ++i)
			clamp_in_place(v.bounds[i], hilo);
		return v;
	}

	template <typename Type, typename RangeValueType = Type, typename RangeBounds = array<RangeValueType,2>>
	constexpr
	Type& clamp_in_place(Type& v, const range<RangeValueType, RangeBounds>& hilo)
	{
		using std::clamp;
		v = clamp(v, hilo.lower(), hilo.upper());
		return v;
	}

	// TODO: here to enable implicit conversion when Type is specified, but this whole business is really dodgy
	template <typename Type, typename RangeValueType = Type>
	constexpr
	Type& clamp_in_place(non_deduced<Type>& v, const non_deduced<range<RangeValueType>>& hilo)
	{
		return clamp_in_place(v,hilo);
	}

	template <typename Type, typename RangeValueType = Type, typename RangeBounds = array<RangeValueType, 2>>
	constexpr
	Type clamp(Type v, const range<RangeValueType,RangeBounds>& hilo)
	{
		clamp_in_place(v, hilo);
		return v;
	}

	template <typename Type, typename Bounds = array<Type,2>>
	constexpr
	range<Type> clamp(range<Type,Bounds> v, const range<Type,Bounds>& hilo)
	{
		clamp_in_place(v, hilo);
		return v;
	}

	class range_operators_compatibility_tag {};

	template <typename Type, typename Lower>
	struct range_upper_bound
	{
		using value_type = Type;

		Type upper;

		constexpr const Type& operator[](size_t i) const
		{
			assert(intersects_lower(range{size_t{},size()}, i));
			if(i == 0)
				return Lower::value;
			else
				return upper;
		};

		constexpr size_t size() const { return 2; }

		constexpr bool operator==(range_upper_bound other)
		{ return upper == other.upper; }
		constexpr bool operator!=(range_upper_bound other)
		{ return !(*this == other); }
	};

	template<typename Lower, typename T = typename Lower::value_type>
	constexpr range<T, range_upper_bound<T, Lower>> make_range_upper(T&& upper)
	{
		return {std::forward<T>(upper)};
	}


} // namespace simple::support

namespace simple
{
	template<typename T, typename B>
	struct support::define_array_operators<support::range<T,B>>
	{
		constexpr static auto enabled_operators = array_operator::none;
		constexpr static auto enabled_right_element_operators =
			array_operator::add |
			array_operator::sub |
			array_operator::mul |
			array_operator::div |
			array_operator::add_eq |
			array_operator::sub_eq |
			array_operator::mul_eq |
			array_operator::div_eq;
		constexpr static auto enabled_left_element_operators = array_operator::none;

		constexpr static size_t size = 2;
		constexpr static auto& get_array(support::range<T,B>& r) noexcept
		{ return r.bounds; }
		constexpr static const auto& get_array(const support::range<T,B>& r) noexcept
		{ return r.bounds; }

		template <typename R, array_operator, typename, bool>
		using result = support::range<R>;

		using compatibility_tag = support::range_operators_compatibility_tag;
	};
} // namespace simple

#endif /* end of include guard */
